"""
@author: 江同学呀
@file:  update_api.py
@date: 2025/3/18 20:59
@desc: 
    https://www.elastic.co/guide/en/elasticsearch/reference/7.17/docs-update.html
"""
from typing import Union, Iterable, Dict, Literal, Type, Optional, Any

from elastic_transport import ObjectApiResponse

from espc.core.core import core
from espc.orm.api.base_api import BaseApi
from espc.orm.model.dsl.search_fields import Source
from espc.orm.model.mapping.field.base_field.base_field import _BaseField
from espc.orm.model.model.base_index import BaseIndex
from espc.orm.model.scripting.script import Script


class UpdateApi(BaseApi):
    """
    更新
    """

    def __init__(self, index: Union[Type[BaseIndex], str], *args, **kwargs):
        # todo
        super().__init__(*args, **kwargs)
        # if index not in ESPrivyCouncil._core._index_dict:
        #     raise ESPCIndexUninitError(f"未初始化{index}")
        self._index: Union[str, Type[BaseIndex]] = index

        self._id: Optional[str] = None
        self._script: Union[str, Dict, Script, None] = None
        self._doc: Optional[Dict[Union[str, _BaseField], Any]] = None

        self._source: Union[Iterable[Union[_BaseField, str]], Source, Dict, bool, None] = None
        self._detect_noop: Optional[bool] = None
        self._upsert: Optional[Dict[Union[str, _BaseField], Any]] = None
        self._scripted_upsert: Optional[bool] = None
        self._doc_as_upsert: Optional[bool] = None
        return

    def id(self, _id: str) -> "UpdateApi":
        """
        设置文档id
        :param _id:
        :return:
        """
        self._id: str = _id
        return self

    def script(self, script: Union[str, Dict, Script]) -> "UpdateApi":
        """
        设置脚本
        :param script:
        :return:
        """
        self._script: Union[str, Dict, Script] = script
        return self

    def doc(self, doc: Dict[Union[str, _BaseField], Any]) -> "UpdateApi":
        """
        设置文档
        :param doc:
        :return:
        """
        self._doc: Dict[Union[str, _BaseField], Any] = doc
        return self

    def source(
            self, fields: Union[_BaseField, str, Iterable[Union[_BaseField, str]], Source, Dict, bool],
            *args: Union[_BaseField, str]
    ) -> "UpdateApi":
        """
        设置返回字段
        :param fields:
        :return:
        """
        if not args:
            self._source: Union[Iterable[Union[_BaseField, str]], Source, Dict, bool] = fields
        elif isinstance(fields, Iterable):
            self._source: Union[Iterable[Union[_BaseField, str]], Source, Dict, bool] = list(fields) + list(args)
        elif isinstance(fields, (_BaseField, str)):
            self._source: Union[Iterable[Union[_BaseField, str]], Source, Dict, bool] = [fields] + list(args)
        else:
            self._source: Union[Iterable[Union[_BaseField, str]], Source, Dict, bool] = fields
        return self

    def detect_noop(self, detect_noop: bool) -> "UpdateApi":
        """
        By default updates that don’t change anything detect that they don’t change anything and return "result": "noop"
        You can disable this behavior by setting "detect_noop": false

        默认情况下，不更改任何内容的更新会检测到它们不会更改任何内容并返回 “result”： “noop”
        您可以通过设置 “detect_noop”： false 来禁用此行为
        :param detect_noop:
        :return:
        """
        self._detect_noop: bool = detect_noop
        return self

    def upsert(self, upsert: Dict[Union[str, _BaseField], Any]) -> "UpdateApi":
        """
        If the document does not already exist, the contents of the upsert element are inserted as a new document.
        If the document exists, the script is executed
        如果文档尚不存在，则 upsert 元素的内容将作为新文档插入。如果文档存在，则执行脚本
        :param upsert:
        :return:
        """
        self._upsert: Dict[Union[str, _BaseField], Any] = upsert
        return self

    def scripted_upsert(self, scripted_upsert: bool) -> "UpdateApi":
        """
        To run the script whether or not the document exists, set scripted_upsert to true
        要在文档是否存在的情况下运行脚本，请将 scripted_upsert 设置为 true
        :param scripted_upsert:
        :return:
        """
        self._scripted_upsert: bool = scripted_upsert
        return self

    def doc_as_upsert(self, doc_as_upsert: bool) -> "UpdateApi":
        """
        Instead of sending a partial doc plus an upsert doc, you can set doc_as_upsert to true to use the contents of
        doc as the upsert value
        您可以将 doc_as_upsert 设置为 true，以使用 doc 的内容作为 upsert 值，而不是发送部分 doc 和 upsert doc
        :param doc_as_upsert:
        :return:
        """
        self._doc_as_upsert: bool = doc_as_upsert
        return self

    def _build(self) -> Dict:
        body: Dict = {}
        if self._script:
            body["script"] = self._script._build() if isinstance(self._script, Script) else self._script
        if self._doc:
            body["doc"] = {f.field_name if isinstance(f, _BaseField) else f: v for f, v in self._doc.items()}
        if self._detect_noop is not None:
            body["detect_noop"] = self._detect_noop
        if self._source:
            body["_source"] = self._source._build() if isinstance(self._source, Source) else self._source
        if self._upsert:
            body["upsert"] = self._upsert
        if self._scripted_upsert is not None:
            body["scripted_upsert"] = self._scripted_upsert
        if self._doc_as_upsert is not None:
            body["doc_as_upsert"] = self._doc_as_upsert
        return body

    def post(
            self, human: bool = None, filter_path: Union[str, Iterable[str]] = None,
            # flat_settings: bool = None,
            error_trace: bool = None, if_seq_no: int = None, if_primary_term: int = None, lang: str = None,
            require_alias: bool = None, refresh: Literal["true", "false", "wait_for"] = None,
            retry_on_conflict: int = None, routing: str = None, source: Union[bool, str, Iterable[str]] = None,
            source_excludes: Union[str, Iterable[str]] = None, source_includes: Union[str, Iterable[str]] = None,
            timeout: str = None, wait_for_active_shards: str = None, *args, **kwargs
    ) -> Dict:
        """
        POST请求
        :param human:
            Statistics are returned in a format suitable for humans (e.g. "exists_time": "1h" or "size": "1kb") and for
            computers (e.g. "exists_time_in_millis": 3600000 or "size_in_bytes": 1024). The human readable values can be
            turned off by adding ?human=false to the query string. This makes sense when the stats results are being
            consumed by a monitoring tool, rather than intended for human consumption. The default for the human flag is
            false.
            统计数据以适合人类的格式返回（例如“exists_time”： “1h”或“size”： “1kb”）和计算机（例如“exists_time_in_millis”： 3600000
            或 “size_in_bytes”： 1024）。可以通过将 ？human=false 添加到查询字符串来关闭人类可读值。当统计结果由监控工具使用而不是供人类
            使用时，这是有意义的。人为标志的默认值为 false。
        :param filter_path:
            All REST APIs accept a filter_path parameter that can be used to reduce the response returned by
            Elasticsearch. This parameter takes a comma separated list of filters expressed with the dot notation.
            所有 REST API 都接受 filter_path 参数，该参数可用于减少 Elasticsearch 返回的响应。此参数采用逗号分隔的过滤器列表，以点表示
            法表示
        # :param flat_settings:
        #     The flat_settings flag affects rendering of the lists of settings. When the flat_settings flag is true,
        #     settings are returned in a flat format.
        #     flat_settings 标志会影响设置列表的呈现。当 flat_settings 标志为 true 时，设置以平面格式返回
        :param error_trace:
            By default when a request returns an error Elasticsearch doesn’t include the stack trace of the error. You
            can enable that behavior by setting the error_trace url parameter to true.
            默认情况下，当请求返回错误时，Elasticsearch 不包含错误的堆栈跟踪。您可以通过将 error_trace url 参数设置为 true 来启用该行为。
        :param if_seq_no:
            (Optional, integer) Only perform the operation if the document has this sequence number. See Optimistic
            concurrency control.
            （可选，整数）仅当文档具有此序列号时才执行该作。请参阅乐观并发控制。
        :param if_primary_term:
            (Optional, integer) Only perform the operation if the document has this primary term. See Optimistic concurrency control.
            （可选，整数）仅当文档具有此主要术语时才执行该作。请参阅乐观并发控制。
        :param lang:
            (Optional, string) The script language. Default: painless.
            （可选，字符串）脚本语言。默认值：painless。
        :param require_alias:
            (Optional, Boolean) If true, the destination must be an index alias. Defaults to false.
            （可选，布尔值）如果为 true，则目标必须是索引别名。默认为 false。
        :param refresh:
            (Optional, enum) If true, Elasticsearch refreshes the affected shards to make this operation visible to
            search, if wait_for then wait for a refresh to make this operation visible to search, if false do nothing
            with refreshes. Valid values: true, false, wait_for. Default: false.
            （可选，枚举）如果为 true，则 Elasticsearch 刷新受影响的分片以使此作对搜索可见，如果wait_for为 true，则等待刷新以使此作对
            搜索可见，如果为 false，则不对刷新执行任何作。有效值：true、false wait_for。默认值：false。
        :param retry_on_conflict:
            (Optional, integer) Specify how many times should the operation be retried when a conflict occurs. Default: 0.
            （可选，整数）指定在发生冲突时应重试作的次数。默认值：0。
        :param routing:
            (Optional, string) Custom value used to route operations to a specific shard.
            （可选，字符串）用于将作路由到特定分片的自定义值。
        :param source:
            (Optional, list) Set to true to enable source retrieval (default: false). You can also specify a
            comma-separated list of the fields you want to retrieve.
            （可选，列表）设置为 true 以启用源检索（默认值：false）。您还可以指定要检索的字段的逗号分隔列表。
        :param source_excludes:
            (Optional, list) Specify the source fields you want to exclude.
            （可选，列表）指定要排除的源字段。
        :param source_includes:
            (Optional, list) Specify the source fields you want to retrieve.
            （可选，列表）指定要检索的源字段。
        :param timeout:
            (Optional, time units) Period to wait for the following operations:
                - Dynamic mapping updates
                - Waiting for active shards
            Defaults to 1m (one minute). This guarantees Elasticsearch waits for at least the timeout before failing.
            The actual wait time could be longer, particularly when multiple waits occur.

            （可选，时间单位）等待以下作的时间段：
                - 动态映射更新
                - 等待活动分片
            默认为 1m（1 分钟）。这保证了 Elasticsearch 在失败之前至少等待超时。实际等待时间可能会更长，尤其是在发生多个等待时。
        :param wait_for_active_shards:
            (Optional, string) The number of shard copies that must be active before proceeding with the operation. Set
            to all or any positive integer up to the total number of shards in the index (number_of_replicas+1).
            Default: 1, the primary shard.
            （可选，字符串）在继续作之前必须处于活动状态的分片副本数。设置为全部或任何正整数，最大为索引中的分片总数
            （number_of_replicas+1）。默认值：1，主分片。
        :param args:
        :param kwargs:
        :return:
        """
        body: Dict = self._build()
        resp: ObjectApiResponse = core.es.update(
            index=self._index if isinstance(self._index, str) else self._index._name,
            id=self._id,
            body=body,
            human=human,
            filter_path=filter_path,
            # flat_settings=flat_settings,
            error_trace=error_trace,
            if_seq_no=if_seq_no,
            if_primary_term=if_primary_term,
            lang=lang,
            require_alias=require_alias,
            refresh=refresh,
            retry_on_conflict=retry_on_conflict,
            routing=routing,
            source=source,
            source_excludes=source_excludes,
            source_includes=source_includes,
            timeout=timeout,
            wait_for_active_shards=wait_for_active_shards,
            *args, **kwargs
        )
        return resp.body
